#! /usr/bin/env python
#  -*- coding: utf-8 -*-

__all__ = ['Animation', 'getCanvasSize', 'reset', 'loadMap', 'drawWaypoint', 'drawRobot', 'pos2CanvasLocation', 
           'deltaPos2DeltaCanvasLocation', 'moveRobotTo', 'update', 'start', 'stop', 'wrapInit', 'wrapUpdate']

import sys

try:
    from Tkinter import *
except ImportError:
    from tkinter import *

try:
    import ttk
    py3 = False
except ImportError:
    import tkinter.ttk as ttk
    py3 = True
    
import time
from vector import Vector
from map import Map
from traj import Traj


class Animation(object):
    def __init__(self, gui):
        """
        创建一个动画对象。
        """
        self._tk = gui.getRoot()
        self._canvas = gui.getCanvas()
        #self._canvas.pack()
        self._canvas_width, self._canvas_height = self.getCanvasSize()
        self._started = False
        self._map = Map('map.txt')
        tr = Traj(0)
        self._traj, self._num = tr.load()
        self.reset()

    def getCanvasSize(self):
        """
        获取canvas的大小。
        :return: canvas的大小(width, height)。
        """
        self.refresh()
        width = self._canvas.winfo_width()
        height = self._canvas.winfo_height()
        return width, height
        
    def reset(self):
        """
        重置画面。让动画界面回到时间为0的时刻。
        """
        self.clear()
        self.loadMap()
        self._t = 0
        self._time_index = [0 for _ in range(self._num)]
        self.redraw()
        
    def clear(self):
        """
        清除canvas上所有内容。
        """
        self._canvas.delete("all")
        self._robots = []
        self._pos = [] # position of robots
        self._obstacles = {}
        self._obstacle_id = 0
        
    def loadMap(self):
        """
        加载地图。
        """
        edges = self._map.getAllEdges()
        print(edges)
        self._min_x, self._min_y, self._max_x, self._max_y = self._map.getXYRegion()
        n = self._map.getNodesNum()
        for i in range(n):
            print('Create node ', self._map.getNode(i))
            self.drawWaypoint(self._map.getNodeCoord(i), label=self._map.getNode(i))

        for e in edges:
            x0, y0 = self.pos2CanvasLocation(self._map.getNodeCoord(e[0]))
            x1, y1 = self.pos2CanvasLocation(self._map.getNodeCoord(e[1]))
            self._canvas.create_line(x0, y0, x1, y1)
            print('create line from ({0}, {1}) to ({2}, {3})'.format(x0, y0, x1, y1))

    def drawWaypoint(self, pos, label=None, waypoint_size=20):
        """
        绘制地图上的节点。
        :param pos: 节点的位置。
        :param label: 节点的标签。
        :param waypoint_size: 节点的大小。
        """
        x, y = self.pos2CanvasLocation(pos)
        self._canvas.create_oval(x-waypoint_size/2, y-waypoint_size/2,
                                 x+waypoint_size/2, y+waypoint_size/2)
        if label is not None:
            self._canvas.create_text(x, y, text=label)
            
    def drawObstacle(self, pos, obstacle_size=15):
        """
        绘制障碍物。
        :param pos: 障碍物的位置。
        :param waypoint_size: 障碍物的大小。
        """
        x, y = self.pos2CanvasLocation(pos)
        obstacle = self._canvas.create_oval(x-obstacle_size/2, y-obstacle_size/2,
                                            x+obstacle_size/2, y+obstacle_size/2,
                                            fill='black', tag=str(self._obstacle_id))
        self._obstacles[self._obstacle_id] = obstacle
        self._obstacle_id += 1
                                 
    def deleteObstacle(self, obstacle_id):
        """
        删除障碍物。
        :param obstacle_id: 障碍物的ID。
        """
        self._canvas.delete(str(obstacle_id))

    def drawRobot(self, pos, robot_size=10, color='red'):
        """
        绘制机器人。
        :param pos: 机器人的位置。
        :param robot_size: 机器人的大小。
        :param color: 机器人的颜色。
        """
        x, y = self.pos2CanvasLocation(pos)
        robot = self._canvas.create_rectangle(x-robot_size/2, y-robot_size/2,
                                            x+robot_size/2, y+robot_size/2,
                                            fill=color)
        self._robots.append(robot)
        self._pos.append(pos)
        print(pos.getX(), pos.getY(), x, y)
        # return the id of the robot
        return len(self._robots)-1

    def pos2CanvasLocation(self, pos, x_margin=20, y_margin=20):
        """
        把实际物理位置转化为canvas上的坐标。
        :param pos: 实际物理位置。
        :param x_margin: canvas区域左右两侧留出来的空白区域的宽度。
        :param y_margin: canvas区域上下两侧流出来的空白区域的高度。
        :return: canvas上的坐标x, y。
        """
        x = (pos.getX()-self._min_x)/(self._max_x-self._min_x)*(self._canvas_width-x_margin*2)+x_margin
        y = (pos.getY()-self._min_y)/(self._max_y-self._min_y)*(self._canvas_height-y_margin*2)+y_margin
        return x, y

    def deltaPos2DeltaCanvasLocation(self, delta_pos, x_margin=20, y_margin=20):
        """
        把实际物理位置之间的距离转化为canvas上的坐标距离（用横坐标、纵坐标之间的距离表示）。
        :param delta_pos: 实际物理位置之间的距离。
        :param x_margin: canvas区域左右两侧留出来的空白区域的宽度。
        :param y_margin: canvas区域上下两侧流出来的空白区域的高度。
        :return: canvas上的水平、竖直距离x, y。
        """
        x = delta_pos.getX()/(self._max_x-self._min_x)*(self._canvas_width-x_margin*2)
        y = delta_pos.getY()/(self._max_y-self._min_y)*(self._canvas_height-y_margin*2)
        return x, y

    def moveRobotTo(self, robot_id, new_x, new_y):
        """
        将下标为robot_id的机器人移动到位置(new_x, new_y)。
        :param robot_id: 机器人的下标。
        :param new_x: 新位置的x坐标。
        :param new_y: 新位置的y坐标。
        """
        dx, dy = self.deltaPos2DeltaCanvasLocation(Vector(new_x, new_y) - self._pos[robot_id])
        print('dx=', dx, 'dy=', dy)
        self._pos[robot_id].update(new_x, new_y)
        self._canvas.move(self._robots[robot_id], dx, dy)

    def refresh(self):
        """
        刷新显示。
        """
        self._tk.update()            
        
    def start(self):
        """
        启动动画。
        """
        self._started = True
        
    def stop(self):
        """
        停止动画。
        """
        self._started = False
    
    def redraw(self):
        """
        重新绘制所有内容。
        """
        self._canvas_width, self._canvas_height = self.getCanvasSize()
        self.loadMap()
        self._max_len = 0
        color = ['blue', 'red', 'green', 'yellow', 'black', 'white']
        for i in range(self._num):
            #if i >= len(color):
            #    #self.drawRobot(Vector(self._traj[i][1], self._traj[i][2]))
            #    self.drawRobot(Vector(self._traj[i][self._time_index[i]+1], self._traj[i][self._time_index[i]+2]))
            #else:
            #    #self.drawRobot(Vector(self._traj[i][1], self._traj[i][2]), color=color[i])
            self.drawRobot(Vector(self._traj[i][self._time_index[i]+1], self._traj[i][self._time_index[i]+2]), color=color[i%len(color)])
            if self._max_len < len(self._traj[i]):
                self._max_len = len(self._traj[i])
                
    def wrapInit(self):
        self.clear()
        self.redraw()
    
    def wrapUpdate(self, dt):            
        if not self._started:
            return 
            
        for j in range(self._num):
            update_time_found = False
            for k in range(self._time_index[j], self._max_len-3, 3):
                print('trajactory time: {0}; actual time: {1}'.format(self._traj[j][k], self._t))
                if self._traj[j][k] > self._t:
                    break
                if self._traj[j][k] <= self._t and self._traj[j][k+3]>self._t:
                    self._time_index[j] = k
                    update_time_found = True
                    print('found trajactory time: {0}'.format(self._traj[j][k]))
                    break
            if update_time_found:
                self.moveRobotTo(j, self._traj[j][self._time_index[j]+1], self._traj[j][self._time_index[j]+2]) 
        self.refresh()
        self._t += dt

class ApplicationGUI:
    def __init__(self, top=None):
        if top is None:
            self._root = top = Tk()
        else:
            self._root = top
        
        '''This class configures and populates the toplevel window.
           top is the toplevel containing window.'''
        _bgcolor = '#d9d9d9'  # X11 color: 'gray85'
        _fgcolor = '#000000'  # X11 color: 'black'
        _compcolor = '#d9d9d9' # X11 color: 'gray85'
        _ana1color = '#d9d9d9' # X11 color: 'gray85' 
        _ana2color = '#d9d9d9' # X11 color: 'gray85' 

        top.geometry("600x420+650+150")
        top.title("Animation")
        top.configure(background="#d9d9d9")
        top.resizable(0, 0) # disable the maximize/minimize button

        self._main_frame = ttk.Frame(top, padding="3 3 12 12")
        self._main_frame.grid(column=0, row=0, sticky=(N, W, E, S))
        top.rowconfigure(0, weight=1)
        top.columnconfigure(0, weight=1)
        
        self._tools_frame = ttk.Frame(self._main_frame)
        self._tools_frame.grid(row=0, column=0, sticky=(N, W, E, S))

        self._animation = None
        self._reset_button = ttk.Button(self._tools_frame, text="Reset", command=self.reset)
        self._reset_button.pack(side=LEFT, fill=Y)
        
        self._trigger_button = ttk.Button(self._tools_frame, text="Start", command=self.trigger)
        self._trigger_button.pack(side=LEFT, fill=Y)

        #self._stop_button = ttk.Button(self._tools_frame, text="Stop", command=self.stop)
        #self._stop_button.pack(side=LEFT, fill=Y)
        
        self._speed_label = ttk.Label(self._tools_frame, text="Speed")
        self._speed_label.pack(side=LEFT, fill=Y)
        
        self._speed = IntVar()
        self._speed.set(5)
        self._speed_scale = ttk.Scale(self._tools_frame, orient=HORIZONTAL, from_=1, to=10, variable=self._speed)
        self._speed_scale.pack(side=LEFT, fill=Y)

        self.menubar = Menu(top,font="TkMenuFont",bg=_bgcolor,fg=_fgcolor)
        top.configure(menu = self.menubar)

        self.file = Menu(top,tearoff=0)
        self.menubar.add_cascade(menu=self.file,
                activebackground="#d9d9d9",
                activeforeground="#000000",
                background="#d9d9d9",
                compound="left",
                font="TkMenuFont",
                foreground="#000000",
                label="File")
        self.file.add_command(
                activebackground="#d8d8d8",
                activeforeground="#000000",
                background="#d9d9d9",
                command=self.open,
                compound="left",
                font="TkMenuFont",
                foreground="#000000",
                label="Open")
        self.file.add_command(
                activebackground="#d8d8d8",
                activeforeground="#000000",
                background="#d9d9d9",
                command=self.save,
                compound="left",
                font="TkMenuFont",
                foreground="#000000",
                label="Save")
        self.file.add_command(
                activebackground="#d8d8d8",
                activeforeground="#000000",
                background="#d9d9d9",
                command=self.quit,
                compound="left",
                font="TkMenuFont",
                foreground="#000000",
                label="Quit")
        self.simulation = Menu(top,tearoff=0)
        self.menubar.add_cascade(menu=self.simulation,
                activebackground="#d9d9d9",
                activeforeground="#000000",
                background="#d9d9d9",
                compound="left",
                font="TkMenuFont",
                foreground="#000000",
                label="Simulation")
        self.help = Menu(top,tearoff=0)
        self.menubar.add_cascade(menu=self.help,
                activebackground="#d9d9d9",
                activeforeground="#000000",
                background="#d9d9d9",
                compound="left",
                font="TkMenuFont",
                foreground="#000000",
                label="Help")
        self.help.add_command(
                activebackground="#d8d8d8",
                activeforeground="#000000",
                background="#d9d9d9",
                command=self.about,
                compound="left",
                font="TkMenuFont",
                foreground="#000000",
                label="About")

        self._canvas_frame = ttk.Frame(self._main_frame)
        self._canvas_frame.grid(column=0, row=1, sticky=(N, W, E, S))
        self._main_frame.columnconfigure(0, weight=1)
        self._main_frame.rowconfigure(1, weight=1)
        self._canvas = Canvas(self._canvas_frame)
        self._canvas.pack(fill = BOTH, expand = True)
        
        self._animation = Animation(self)
        self._animation.wrapInit()
        top.bind("<Configure>", self.resize)

    def getRoot(self):
        return self._root
        
    def getCanvas(self):
        return self._canvas
        
    def updateCanvas(self):
        self._animation.wrapUpdate(0.1 * self._speed.get())
        self._root.after(20, self.updateCanvas)
    
    def reset(self):
        if self._animation is not None:
            self._animation.stop()
            self._trigger_button['text'] = 'Start'
            self._animation.reset()
            
    def trigger(self):
        if self._trigger_button['text'] == 'Start':
            if self._animation is not None:
                self._animation.start()
                self._trigger_button['text'] = 'Stop'
        else:
            if self._animation is not None:
                self._animation.stop()
                self._trigger_button['text'] = 'Start'
            
    def resize(self, event):
        self._animation.wrapInit()

    def run(self):
        self.updateCanvas()
        self._root.mainloop()

    def about(self):
        print('about')
        sys.stdout.flush()

    def open(self):
        print('open')
        sys.stdout.flush()

    def quit(self):
        self.destroyWindow()
        sys.stdout.flush()

    def save(self):
        print('save')
        sys.stdout.flush()

    def destroyWindow(self):
        # Function which closes the window.
        self._root.destroy()
        self._root = None

if __name__ == '__main__':
    gui = ApplicationGUI()
    gui.run()


